#include "StdAfx.h"
#include "Renderer.h"
#include "Scene.h"
#include "Ray.h"
#include "Random.h"

DATA_TYPE CRenderer::ms_fGlobalTime = 0.f;

CRenderer::CRenderer(void)
: m_pPixels(NULL)
, m_samplingMode(SM_ADAPTIVE)
, m_uiMaxReflection(MAX_REFLECTION)
, m_bRefractionEnabled(true)
, m_bReflectionEnabled(true)
, m_backgroundColor(0.f, 0.f, 0.f, 1.f)
, m_fDepthOfFieldValue(FOCAL_LENGTH)
, m_fMotionBlurValue(MOTION_BLUR_VALUE)
, m_bMotionBlurEnabled(false)
, m_bDepthOfFieldEnabled(false)
, m_iNextRenderLine(-1)
, m_bUseKDTree(true)
{
}

CRenderer::~CRenderer(void)
{
	if (m_pPixels)
		delete[] m_pPixels;
}

void CRenderer::Init()
{
	m_pPixels = new unsigned char[WINDOW_WIDTH * WINDOW_HEIGHT * 3];

}

void CRenderer::DoRender()
{
	m_iNextRenderLine = WINDOW_HEIGHT - 1;
}

void CRenderer::Render(const CScene* pScene)
{
	m_pScene = pScene;

	static DWORD dwTime = 0; 
	if (m_iNextRenderLine != -1)
	{
		dwTime = GetTickCount();
		while(m_iNextRenderLine != -1)
		{
			SetGlobalTime(0.f);

			for (int i = 0; i < WINDOW_WIDTH; ++i)
			{
				CColor4 pixelColor;

				if (m_bDepthOfFieldEnabled || m_bMotionBlurEnabled)
				{
					for (int k = 0; k < NUM_SCREEN_EFFECT_SAMPLES; ++k)
					{
						if (m_bMotionBlurEnabled)
						{
							SetGlobalTime(CRandom::GetInstance().GetRandomFloat(0.f, m_fMotionBlurValue));
						}

						pixelColor += GetPixelColor((DATA_TYPE)i, (DATA_TYPE)m_iNextRenderLine);
					}
					pixelColor /= NUM_SCREEN_EFFECT_SAMPLES;
				}
				else
				{
					pixelColor = GetPixelColor((DATA_TYPE)i, (DATA_TYPE)m_iNextRenderLine);
				}

				m_pPixels[(i + m_iNextRenderLine * WINDOW_WIDTH) * 3] = (unsigned char)clamp((pixelColor.r * 255.f), 0.f, 255.f);
				m_pPixels[(i + m_iNextRenderLine * WINDOW_WIDTH) * 3 + 1] = (unsigned char)clamp((pixelColor.g * 255.f), 0.f, 255.f);
				m_pPixels[(i + m_iNextRenderLine * WINDOW_WIDTH) * 3 + 2] = (unsigned char)clamp((pixelColor.b * 255.f), 0.f, 255.f);
			}

			if (m_iNextRenderLine % 100 == 0)
				cout << "line : " << m_iNextRenderLine << endl;

			--m_iNextRenderLine;
		}

		DWORD dwElapsed = GetTickCount() - dwTime;
		cout << "Render Time : " << dwElapsed << "ms" << endl;
	}

	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glDrawPixels(WINDOW_WIDTH, WINDOW_HEIGHT, GL_RGB,GL_UNSIGNED_BYTE, m_pPixels);

}

CColor4 CRenderer::GetPixelColor(DATA_TYPE pixelX, DATA_TYPE pixelY)
{
	CColor4 pixelColor;

	switch (m_samplingMode)
	{
	case SM_BASIC:
		pixelColor = GetColor(pixelX + 0.5f, pixelY + 0.5f);
		break;

	case SM_JITTERED:
		// get 4 samples from 4 cells
		pixelColor += GetColor(pixelX + CRandom::GetInstance().GetRandomFloat(0.f, 0.5f), 
							   pixelY + CRandom::GetInstance().GetRandomFloat(0.f, 0.5f));
		pixelColor += GetColor(pixelX + 0.5f + CRandom::GetInstance().GetRandomFloat(0.f, 0.5f), 
							   pixelY + CRandom::GetInstance().GetRandomFloat(0.f, 0.5f));
		pixelColor += GetColor(pixelX + CRandom::GetInstance().GetRandomFloat(0.f, 0.5f), 
							   pixelY + 0.5f + CRandom::GetInstance().GetRandomFloat(0.f, 0.5f));
		pixelColor += GetColor(pixelX + 0.5f + CRandom::GetInstance().GetRandomFloat(0.f, 0.5f), 
							   pixelY + 0.5f + CRandom::GetInstance().GetRandomFloat(0.f, 0.5f));
		pixelColor /= 4;
		break;

	case SM_ADAPTIVE:
		pixelColor = GetColorAdaptiveSS(pixelX, pixelY, 1.f);
		break;
	}

	return pixelColor;
}

CColor4 CRenderer::GetColorAdaptiveSS(DATA_TYPE x, DATA_TYPE y, DATA_TYPE size)
{
	DATA_TYPE fHalfSize = 0.5f * size;

	CColor4 pixelColorAvg;
	pixelColorAvg += GetColor(x, y);
	pixelColorAvg += GetColor(x + size, y);
	pixelColorAvg += GetColor(x, y + size);
	pixelColorAvg += GetColor(x + size, y + size);
	pixelColorAvg /= 4;

	if (size < MAX_ADAPATIVE)
	{
		return pixelColorAvg;
	}

	CColor4 pixelColorCenter = GetColor(x + fHalfSize, y + fHalfSize);

	DATA_TYPE fDiff = pixelColorAvg.GetSquaredColorDistance(pixelColorCenter);

	if (fDiff > EPSILON)
	{
		CColor4 newPixelColorAvg;
		newPixelColorAvg += GetColorAdaptiveSS(x, y, fHalfSize);
		newPixelColorAvg += GetColorAdaptiveSS(x + fHalfSize, y, fHalfSize);
		newPixelColorAvg += GetColorAdaptiveSS(x, y + fHalfSize, fHalfSize);
		newPixelColorAvg += GetColorAdaptiveSS(x + fHalfSize, y + fHalfSize, fHalfSize);
		newPixelColorAvg /= 4;
		return newPixelColorAvg;
	}
	else
	{
		return pixelColorAvg;
	}
}

CColor4 CRenderer::GetColor(DATA_TYPE x, DATA_TYPE y)
{
	CColor4 pixelColor;

	// optimization required
	const CCameraPtr& spCamera = m_pScene->GetCameras().front();

	const int iHalfHeight = WINDOW_HEIGHT / 2;
	const int iHalfWidth = WINDOW_WIDTH / 2;

	const CVector3D& vOriginalCameraPos = spCamera->GetPosition();
	const CVector3D& vCameraDir = spCamera->GetDir();
	const CVector3D& vCameraRight = spCamera->GetRight();
	const CVector3D& vCameraUp = spCamera->GetUp();

	DATA_TYPE fPlaneHalfWidth = tan(DEG2RAD(spCamera->GetFov() / 2)) * m_fDepthOfFieldValue;
	DATA_TYPE fPixelSize = fPlaneHalfWidth / iHalfWidth;

	DATA_TYPE fApertureSize = fPixelSize * APERTURE_RATIO;

	CVector3D vCameraPos = vOriginalCameraPos;

	if (m_bDepthOfFieldEnabled)
	{
		vCameraPos = vCameraPos
		+ CRandom::GetInstance().GetRandomFloat(-0.5f, 0.5f) * fApertureSize * vCameraRight
		+ CRandom::GetInstance().GetRandomFloat(-0.5f, 0.5f) * fApertureSize * vCameraUp;
	}

	const CVector3D vCenterPoint = vOriginalCameraPos + m_fDepthOfFieldValue * vCameraDir;

	// Assume FOV is horizontal
	DATA_TYPE fNormalizedX = (x - iHalfWidth) / iHalfWidth;
	DATA_TYPE fNormalizedY = (y - iHalfHeight) / iHalfWidth;

	CVector3D vPointOnPlane = vCenterPoint 
		+ fNormalizedX * fPlaneHalfWidth * vCameraRight
		+ fNormalizedY * fPlaneHalfWidth * vCameraUp;
	
	if (m_bDepthOfFieldEnabled)
	{
		vPointOnPlane = vPointOnPlane
			+ CRandom::GetInstance().GetRandomFloat(-0.5f, 0.5f) * fPixelSize * vCameraRight
			+ CRandom::GetInstance().GetRandomFloat(-0.5f, 0.5f) * fPixelSize * vCameraUp;
	}
	

	CVector3D vCameraRayDir = (vPointOnPlane - vCameraPos).Normalize();
	CRay ray(vCameraPos, vCameraRayDir);

	unsigned int uiReflections = 0;
	DATA_TYPE fReflectance = 1.f;
	DATA_TYPE fAlpha = 1.f;
	
	pixelColor = GetColor(ray, 0, 1.f);

	return pixelColor;
}

CColor4 CRenderer::GetColor(CRay ray, unsigned int uiReflections, DATA_TYPE fPrevRefraction)
{
	const CKdTree& kdTree = m_pScene->GetKdTree();
	vector<CGeometryPtr> vGeometries;

	if (m_bUseKDTree)
	{
		kdTree.RayIntersection(ray, vGeometries);
	}
	else
	{
		vGeometries = m_pScene->GetGeometries();
	}

	CColor4 shadedColor;
	CColor4 reflectionColor;
	CColor4 refractionColor;

	DATA_TYPE fAlpha = 1.f;

	// find nearset object
	DATA_TYPE tMin = std::numeric_limits<DATA_TYPE>::max();
	vector<CGeometryPtr>::const_iterator it = vGeometries.begin();
	vector<CGeometryPtr>::const_iterator itFind = vGeometries.end();
	bool bInsideMin = false;
	CVector3D vNormalMin;
	for (; it != vGeometries.end(); ++it)
	{
		const CGeometryPtr& spGeometry = *it;
		DATA_TYPE t = 0.f;
		bool bInside = false;
		CVector3D vNormal;
		if (spGeometry->IsIntersect(ray, t, bInside, vNormal) && t < tMin && t > EPSILON)
		{
			tMin = t;
			itFind = it;
			bInsideMin = bInside;
			vNormalMin = vNormal;
		}
	}

	if (tMin != std::numeric_limits<DATA_TYPE>::max())
	{
		// has an intersection
		const CGeometryPtr& spGeometry = *itFind;
		const vector<CLightPtr>& vLights = m_pScene->GetLights();
		vector<CLightPtr>::const_iterator itLight = vLights.begin();
		const CVector3D vPoint = ray.GetOrigin() + tMin * ray.GetDir();
		CVector3D vNormal = vNormalMin;
		if (bInsideMin)
		{
			vNormal = -vNormal;
		}
		
		DATA_TYPE fTransparency = 1.f;
		vector<CGeometryPtr> vGeometries2;
		for (; itLight != vLights.end(); ++itLight)
		{
			const CLightPtr& spLight = *itLight;
			DATA_TYPE fDistanceToLight;
			CRay rayToLight = spLight->GetRayToLightAndDistance(vPoint, fDistanceToLight);

			if (m_bUseKDTree)
				kdTree.RayIntersection(rayToLight, vGeometries2);
			else
				vGeometries2 = m_pScene->GetGeometries();
			for (it = vGeometries2.begin(); it != vGeometries2.end(); ++it)
			{
				const CGeometryPtr& spObstacle = *it;
				DATA_TYPE t = 0.f;
				bool bInside;
				CVector3D vPtNormal;
				if (spObstacle->IsIntersect(rayToLight, t, bInside, vPtNormal) && t < fDistanceToLight && t > EPSILON)
				{
					fTransparency *= (1.f - spObstacle->GetColor().a);
					if (fTransparency < EPSILON)
						break;
				}
			}

			if (!bInsideMin)
			{
				shadedColor += spLight->Shade(ray, vPoint, vNormal, *spGeometry, fTransparency);
			}
		}	

		// compute reflection
		if (m_bReflectionEnabled && uiReflections < m_uiMaxReflection)
		{
			CVector3D vReflectionDir = ray.GetDir() - 2.f * ray.GetDir().Dot(vNormal) * vNormal;
			CRay reflectionRay(vPoint + EPSILON * vReflectionDir, vReflectionDir);

			if (!bInsideMin)
			{
				reflectionColor = GetColor(reflectionRay, uiReflections + 1, fPrevRefraction) 
					* spGeometry->GetMaterial().reflectance;
			}
		}

		// compute refraction & transparency
		fAlpha = spGeometry->GetColor().a;
		if (fAlpha != 1.f)
		{
			// refraction
			if (m_bRefractionEnabled)
			{
				DATA_TYPE fCosIn = vNormal.Dot(-ray.GetDir());
				DATA_TYPE fNextFraction = bInsideMin ? 1.f : spGeometry->GetMaterial().refraction;
				DATA_TYPE n = fPrevRefraction / fNextFraction;
				DATA_TYPE fCosOutSquared = 1 - (n * n) * (1 - fCosIn * fCosIn);
				if (fCosOutSquared > 0.f)
				{
					DATA_TYPE fCosOut = sqrtf(fCosOutSquared);
					CVector3D vRefractionDir = n * ray.GetDir() + (n * fCosIn - fCosOut) * vNormal;
					vRefractionDir = vRefractionDir.Normalize();

					CRay refractionRay(vPoint + EPSILON * ray.GetDir(), vRefractionDir);
					refractionColor = GetColor(refractionRay, uiReflections, 1.f);
				}
			}
			// just transparency
			else
			{
				CRay refractionRay(vPoint + EPSILON * ray.GetDir(), ray.GetDir());
				refractionColor = GetColor(refractionRay, uiReflections, 1.f);
			}
			if (!bInsideMin)
			{
				refractionColor = refractionColor * (1.f - fAlpha);
			}
		}
	}
	else
	{
		// no intersection with an object
		shadedColor = m_backgroundColor;
	}

	return (shadedColor + reflectionColor) * fAlpha + refractionColor;
}